Skip to content

Conversation

@dahlia
Copy link
Member

@dahlia dahlia commented Feb 8, 2026

Summary

Closes #548

When an activity delivery permanently fails (e.g., the remote inbox returns 410 Gone or 404 Not Found), retrying is pointless. This PR adds a callback mechanism so applications can be notified of such failures and clean up unreachable followers.

Changes

  • Added OutboxPermanentFailureHandler callback type, registered via Federatable.setOutboxPermanentFailureHandler(), which receives the failed inbox URL, activity, HTTP status code, error, and actor IDs.
  • Added SendActivityError class (exported from @fedify/fedify/federation) that captures the HTTP status code, inbox URL, and response body when delivery fails.
  • Added permanentFailureStatusCodes option to FederationOptions to customize which HTTP status codes are treated as permanent failures (defaults to [404, 410]).
  • Added actorIds field to OutboxMessage to propagate recipient actor identifiers through the queue system.
  • Permanent failures now automatically skip retries, even when no handler is registered.
  • Added setOutboxPermanentFailureHandler() no-op to MockFederation in @fedify/testing.

Testing

  • 8 new test cases for permanent failure handling in packages/fedify/src/federation/middleware.test.ts
  • 3 new test cases for SendActivityError in packages/fedify/src/federation/send.test.ts

@issues-auto-labeler issues-auto-labeler bot added component/federation Federation object related component/mq Message queue related component/outbox Outbox related labels Feb 8, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @dahlia, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly improves the robustness of activity delivery by introducing a dedicated mechanism for handling permanent delivery failures. It allows applications to react to unrecoverable errors, such as remote inboxes returning '404 Not Found' or '410 Gone', by providing a customizable callback. This ensures that resources are not wasted on futile retry attempts and enables applications to maintain a cleaner state of their follower relationships.

Highlights

  • Permanent Delivery Failure Handling: Introduced a mechanism to handle permanent activity delivery failures (e.g., HTTP 404 Not Found, 410 Gone) by allowing applications to register a callback (OutboxPermanentFailureHandler). This enables cleaning up unreachable followers and prevents pointless retries.
  • Customizable Permanent Failure Status Codes: Added a permanentFailureStatusCodes option to FederationOptions, allowing customization of which HTTP status codes are considered permanent failures. The default codes are 404 and 410.
  • Structured Error for Delivery Failures: Created a new SendActivityError class that provides structured information about delivery failures, including the HTTP status code, inbox URL, and response body, making error handling more robust.
  • Actor ID Propagation: Enhanced OutboxMessage with an actorIds field to propagate recipient actor identifiers through the queue system, which is crucial for the permanent failure handler to identify which followers to clean up, especially with shared inboxes.
  • Automatic Retry Skipping: Configured the system to automatically skip retries for permanent failures, even if no specific handler is registered, optimizing resource usage.
Changelog
  • CHANGES.md
    • Added changelog entries for Federatable.setOutboxPermanentFailureHandler(), permanentFailureStatusCodes option, and SendActivityError class.
  • docs/manual/federation.md
    • Added documentation for the new permanentFailureStatusCodes option in FederationOptions.
  • docs/manual/send.md
    • Introduced a new section detailing 'Permanent delivery failure handling', including usage examples for setOutboxPermanentFailureHandler() and explanations of the values passed to the handler.
  • packages/fedify/src/federation/builder.ts
    • Imported OutboxPermanentFailureHandler type.
    • Added outboxPermanentFailureHandler property to FederationBuilderImpl.
    • Implemented setOutboxPermanentFailureHandler method to register the permanent failure handler.
  • packages/fedify/src/federation/callback.ts
    • Defined the OutboxPermanentFailureHandler type, specifying its parameters (context, inbox URL, activity, error, status code, actor IDs) and behavior.
  • packages/fedify/src/federation/federation.ts
    • Imported OutboxPermanentFailureHandler type.
    • Added setOutboxPermanentFailureHandler method to the Federatable interface.
    • Added permanentFailureStatusCodes property to the FederationOptions interface.
  • packages/fedify/src/federation/middleware.test.ts
    • Added 8 new test cases for FederationImpl.processQueuedTask() to verify permanent failure handling, including scenarios for 410 Gone, 404 Not Found, custom status codes, handler exceptions, and retry skipping behavior.
  • packages/fedify/src/federation/middleware.ts
    • Imported SendActivityError.
    • Added permanentFailureStatusCodes property to FederationImpl and initialized it with default values (404, 410).
    • Implemented logic within processQueuedTask to detect SendActivityError with permanent status codes, call the registered outboxPermanentFailureHandler, and prevent message re-enqueuing for retries.
    • Added actorIds to the OutboxMessage when creating new messages for the queue.
  • packages/fedify/src/federation/mod.ts
    • Exported the new SendActivityError class.
  • packages/fedify/src/federation/queue.ts
    • Added an optional actorIds property (array of strings) to the OutboxMessage interface.
  • packages/fedify/src/federation/send.test.ts
    • Added 3 new test cases for sendActivity() to confirm that SendActivityError is thrown on delivery failures, specifically for 500 Internal Server Error, 410 Gone, and 404 Not Found responses.
  • packages/fedify/src/federation/send.ts
    • Defined the SendActivityError class, extending Error and including inbox, statusCode, and responseBody properties.
    • Modified sendActivityInternal to throw SendActivityError instead of a generic Error when an HTTP response indicates a failure.
  • packages/testing/src/mock.ts
    • Added a no-operation setOutboxPermanentFailureHandler method to MockFederation for testing purposes.
Activity
  • The pull request introduces a new OutboxPermanentFailureHandler callback and SendActivityError class to manage permanent delivery failures.
  • Extensive documentation has been added to CHANGES.md, docs/manual/federation.md, and docs/manual/send.md to explain the new features.
  • The core federation logic in packages/fedify/src/federation/middleware.ts was updated to incorporate permanent failure detection and handling.
  • The OutboxMessage structure was extended to include actorIds for better context in failure handling.
  • A total of 11 new test cases were added across middleware.test.ts and send.test.ts to ensure the correct behavior of permanent failure handling and the SendActivityError.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a robust mechanism for handling permanent delivery failures, which is a valuable addition to the library. The changes are well-structured, including a new SendActivityError class for structured error information, an OutboxPermanentFailureHandler for application-level logic, and comprehensive test coverage. The documentation updates are also clear and helpful. I have one suggestion to make the permanent failure handling even more robust by defensively parsing URLs from the message queue.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds first-class handling for permanent outbox delivery failures (e.g., 404, 410) so applications can be notified and retries can be skipped, reducing wasted delivery attempts and enabling cleanup of unreachable followers.

Changes:

  • Introduces OutboxPermanentFailureHandler and Federatable.setOutboxPermanentFailureHandler() plus permanentFailureStatusCodes (default [404, 410]) to treat certain HTTP responses as non-retriable.
  • Adds SendActivityError to carry structured delivery-failure details (inbox URL, status code, response body).
  • Propagates recipient actorIds through the outbox queue message so handlers can clean up the right followers; updates tests and docs accordingly.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/testing/src/mock.ts Adds a no-op setOutboxPermanentFailureHandler() to keep the testing mock API-compatible.
packages/fedify/src/federation/send.ts Throws SendActivityError on non-OK responses and defines the new error type.
packages/fedify/src/federation/send.test.ts Adds assertions that failures throw SendActivityError with structured fields.
packages/fedify/src/federation/queue.ts Extends OutboxMessage to optionally include actorIds.
packages/fedify/src/federation/mod.ts Exports SendActivityError from the federation module entrypoint.
packages/fedify/src/federation/middleware.ts Implements permanent-failure detection, handler invocation, and “no retry” behavior; enqueues actorIds.
packages/fedify/src/federation/middleware.test.ts Adds coverage for permanent-failure handling, custom status codes, and retry-skipping behavior.
packages/fedify/src/federation/federation.ts Adds the new handler registration method and the permanentFailureStatusCodes option to public types/docs.
packages/fedify/src/federation/callback.ts Introduces the OutboxPermanentFailureHandler callback type.
packages/fedify/src/federation/builder.ts Stores/clones the permanent failure handler; adds setter on the builder base class.
docs/manual/send.md Documents permanent failure handling and permanentFailureStatusCodes.
docs/manual/federation.md Documents the permanentFailureStatusCodes option.
CHANGES.md Adds changelog entries for the new handler, option, and SendActivityError.

dahlia added a commit to dahlia/fedify-fork that referenced this pull request Feb 8, 2026
The permanent failure handler is only invoked when the error is a
SendActivityError, so the type should reflect that instead of the
generic Error.  This lets consumers access statusCode and
responseBody without unsafe casts, and aligns the type with the
documentation.

fedify-dev#559 (comment)

Co-Authored-By: Claude <noreply@anthropic.com>
dahlia added a commit to dahlia/fedify-fork that referenced this pull request Feb 8, 2026
The docs and JSDoc said handler exceptions are "silently" caught and
ignored, but the implementation actually logs them with logger.error().
Updated the wording to say "caught, logged, and ignored" to
accurately reflect the behavior.

fedify-dev#559 (comment)

Co-Authored-By: Claude <noreply@anthropic.com>
dahlia added a commit to dahlia/fedify-fork that referenced this pull request Feb 8, 2026
Update "handler exception is silently ignored" to
"handler exception is caught and logged" to be consistent
with the updated wording elsewhere.

See also: fedify-dev#559 (comment)

Co-Authored-By: Claude <noreply@anthropic.com>
@dahlia dahlia requested a review from Copilot February 8, 2026 11:50
@dahlia
Copy link
Member Author

dahlia commented Feb 8, 2026

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a robust mechanism for handling permanent delivery failures, which is a valuable addition to prevent pointless retries and allow applications to clean up unreachable followers. The changes are well-structured, including a new OutboxPermanentFailureHandler, a custom SendActivityError for structured error information, and configuration options for status codes. The addition of actorIds to OutboxMessage is a thoughtful touch for handling shared inboxes. The implementation is backed by a comprehensive set of new tests, which is great to see. I have one suggestion to improve the maintainability of the new test suite.

@codecov
Copy link

codecov bot commented Feb 8, 2026

Codecov Report

❌ Patch coverage is 95.29412% with 4 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
packages/fedify/src/federation/middleware.ts 96.29% 2 Missing ⚠️
packages/testing/src/mock.ts 0.00% 2 Missing ⚠️
Files with missing lines Coverage Δ
packages/fedify/src/federation/builder.ts 47.23% <100.00%> (ø)
packages/fedify/src/federation/mod.ts 100.00% <100.00%> (ø)
packages/fedify/src/federation/send.ts 98.20% <100.00%> (ø)
packages/fedify/src/federation/middleware.ts 95.37% <96.29%> (ø)
packages/testing/src/mock.ts 65.66% <0.00%> (ø)
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

dahlia added a commit to dahlia/fedify-fork that referenced this pull request Feb 8, 2026
The onOutboxError callback is typed as returning
void | Promise<void>, but was not awaited, unlike the new
outboxPermanentFailureHandler. This could cause unhandled
promise rejections if the callback is async.

Also guard context creation with a null check on the handler
to avoid unnecessary work when no handler is registered.

See also:

  fedify-dev#559 (comment)
  fedify-dev#559 (comment)

Co-Authored-By: Claude <noreply@anthropic.com>
dahlia added a commit to dahlia/fedify-fork that referenced this pull request Feb 8, 2026
Add setup() and createOutboxMessage() helpers for the
permanent failure test suite to eliminate repeated KV store,
queue, and federation initialization across all test steps.

See also: fedify-dev#559 (comment)

Co-Authored-By: Claude <noreply@anthropic.com>
dahlia added a commit to dahlia/fedify-fork that referenced this pull request Feb 8, 2026
The permanent failure handler is only invoked when the error is a
SendActivityError, so the type should reflect that instead of the
generic Error.  This lets consumers access statusCode and
responseBody without unsafe casts, and aligns the type with the
documentation.

fedify-dev#559 (comment)

Co-Authored-By: Claude <noreply@anthropic.com>
dahlia added a commit to dahlia/fedify-fork that referenced this pull request Feb 8, 2026
The docs and JSDoc said handler exceptions are "silently" caught and
ignored, but the implementation actually logs them with logger.error().
Updated the wording to say "caught, logged, and ignored" to
accurately reflect the behavior.

fedify-dev#559 (comment)

Co-Authored-By: Claude <noreply@anthropic.com>
dahlia added a commit to dahlia/fedify-fork that referenced this pull request Feb 8, 2026
Update "handler exception is silently ignored" to
"handler exception is caught and logged" to be consistent
with the updated wording elsewhere.

See also: fedify-dev#559 (comment)

Co-Authored-By: Claude <noreply@anthropic.com>
dahlia added a commit to dahlia/fedify-fork that referenced this pull request Feb 8, 2026
The onOutboxError callback is typed as returning
void | Promise<void>, but was not awaited, unlike the new
outboxPermanentFailureHandler. This could cause unhandled
promise rejections if the callback is async.

Also guard context creation with a null check on the handler
to avoid unnecessary work when no handler is registered.

See also:

  fedify-dev#559 (comment)
  fedify-dev#559 (comment)

Co-Authored-By: Claude <noreply@anthropic.com>
@dahlia dahlia force-pushed the feature/permanent-delivery-failures branch from 6357309 to 39b87b2 Compare February 8, 2026 14:30
dahlia added a commit to dahlia/fedify-fork that referenced this pull request Feb 8, 2026
Add setup() and createOutboxMessage() helpers for the
permanent failure test suite to eliminate repeated KV store,
queue, and federation initialization across all test steps.

See also: fedify-dev#559 (comment)

Co-Authored-By: Claude <noreply@anthropic.com>
dahlia and others added 5 commits February 9, 2026 01:44
When an activity delivery permanently fails (e.g., the remote inbox
returns 410 Gone or 404 Not Found), retrying is pointless.  This
change adds a mechanism for applications to be notified of such
failures so they can clean up unreachable followers.

Key additions:

 -  OutboxPermanentFailureHandler callback type, registered via
    Federatable.setOutboxPermanentFailureHandler(), which receives
    the failed inbox URL, activity, status code, and actor IDs.

 -  SendActivityError class (exported from @fedify/fedify/federation)
    that captures HTTP status code, inbox URL, and response body
    when delivery fails.

 -  permanentFailureStatusCodes option in FederationOptions to
    customize which HTTP status codes are treated as permanent
    failures (defaults to [404, 410]).

 -  actorIds field on OutboxMessage to propagate recipient actor
    identifiers through the queue system.

 -  Automatic retry suppression for permanent failures, even when
    no handler is registered.

Closes fedify-dev#548

Co-Authored-By: Claude <noreply@anthropic.com>
The permanent failure handler is only invoked when the error is a
SendActivityError, so the type should reflect that instead of the
generic Error.  This lets consumers access statusCode and
responseBody without unsafe casts, and aligns the type with the
documentation.

fedify-dev#559 (comment)

Co-Authored-By: Claude <noreply@anthropic.com>
The docs and JSDoc said handler exceptions are "silently" caught and
ignored, but the implementation actually logs them with logger.error().
Updated the wording to say "caught, logged, and ignored" to
accurately reflect the behavior.

fedify-dev#559 (comment)

Co-Authored-By: Claude <noreply@anthropic.com>
Update "handler exception is silently ignored" to
"handler exception is caught and logged" to be consistent
with the updated wording elsewhere.

See also: fedify-dev#559 (comment)

Co-Authored-By: Claude <noreply@anthropic.com>
The onOutboxError callback is typed as returning
void | Promise<void>, but was not awaited, unlike the new
outboxPermanentFailureHandler. This could cause unhandled
promise rejections if the callback is async.

Also guard context creation with a null check on the handler
to avoid unnecessary work when no handler is registered.

See also:

  fedify-dev#559 (comment)
  fedify-dev#559 (comment)

Co-Authored-By: Claude <noreply@anthropic.com>
Add setup() and createOutboxMessage() helpers for the
permanent failure test suite to eliminate repeated KV store,
queue, and federation initialization across all test steps.

See also: fedify-dev#559 (comment)

Co-Authored-By: Claude <noreply@anthropic.com>
@dahlia dahlia force-pushed the feature/permanent-delivery-failures branch from 39b87b2 to b50404e Compare February 8, 2026 16:44
@github-actions
Copy link
Contributor

github-actions bot commented Feb 8, 2026

Pre-release has been published for this pull request:

Packages

Package Version JSR npm
@fedify/fedify 2.0.0-pr.559.6+b50404e9 JSR npm
@fedify/cli 2.0.0-pr.559.6+b50404e9 JSR npm
@fedify/amqp 2.0.0-pr.559.6+b50404e9 JSR npm
@fedify/cfworkers 2.0.0-pr.559.6+b50404e9 JSR npm
@fedify/denokv 2.0.0-pr.559.6+b50404e9 JSR
@fedify/elysia 2.0.0-pr.559.6+b50404e9 JSR npm
@fedify/express 2.0.0-pr.559.6+b50404e9 JSR npm
@fedify/fastify 2.0.0-pr.559.6+b50404e9 JSR npm
@fedify/fixture 2.0.0-pr.559.6+b50404e9 JSR
@fedify/fresh 2.0.0-pr.559.6+b50404e9 JSR
@fedify/h3 2.0.0-pr.559.6+b50404e9 JSR npm
@fedify/hono 2.0.0-pr.559.6+b50404e9 JSR npm
@fedify/koa 2.0.0-pr.559.6+b50404e9 JSR npm
@fedify/lint 2.0.0-pr.559.6+b50404e9 JSR npm
@fedify/nestjs 2.0.0-pr.559.6+b50404e9 npm
@fedify/next 2.0.0-pr.559.6+b50404e9 npm
@fedify/postgres 2.0.0-pr.559.6+b50404e9 JSR npm
@fedify/redis 2.0.0-pr.559.6+b50404e9 JSR npm
@fedify/relay 2.0.0-pr.559.6+b50404e9 JSR npm
@fedify/sqlite 2.0.0-pr.559.6+b50404e9 JSR npm
@fedify/sveltekit 2.0.0-pr.559.6+b50404e9 JSR npm
@fedify/testing 2.0.0-pr.559.6+b50404e9 JSR npm
@fedify/vocab 2.0.0-pr.559.6+b50404e9 JSR npm
@fedify/vocab-runtime 2.0.0-pr.559.6+b50404e9 JSR npm
@fedify/vocab-tools 2.0.0-pr.559.6+b50404e9 JSR npm
@fedify/webfinger 2.0.0-pr.559.6+b50404e9 JSR npm

Documentation

The docs for this pull request have been published:

https://0bd22bff.fedify.pages.dev

@dahlia dahlia merged commit c39fbd8 into fedify-dev:main Feb 8, 2026
30 of 31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component/federation Federation object related component/mq Message queue related component/outbox Outbox related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add handler for permanent delivery failures (410 Gone, 404 Not Found)

1 participant